//
//  AFMarkdownStringConvertor.swift
//  TextDemo
//
//  Created by 许海峰 on 2021/6/24.
//

import Foundation
public class AFMarkdownStringConvertor: NSObject {
    public class ResultInPlain {
        public var result: NSTextCheckingResult
        public var name: String
        public var id: String
        public init(result:NSTextCheckingResult, name:String, id:String) {
            self.result = result
            self.name = name
            self.id = id
        }
    }
    
    private let topicPattern = "#\\[(.+?)\\]\\(tid=([0-9a-zA-Z]+?)\\)#"
    private let atUsersPattern = "@\\[(.+?)\\]\\(uid=([0-9a-zA-Z]+?)\\)@"
    private var topicResultInMarkdown:NSTextCheckingResult?
    private var atUserResultsInMarkdown:[NSTextCheckingResult] = []
    private var allResultsInMarkdown:[NSTextCheckingResult] {
        get {
            var allResults = atUserResultsInMarkdown
            if let topicResult = topicResultInMarkdown {
                allResults.append(topicResult)
                allResults.sort { r1, r2 in
                    r1.range.location < r2.range.location
                }
            }
            return allResults
        }
    }
    
    public var markdownText = "" {
        didSet {
            checkRegularExpression()
        }
    }
    public var topicResultInPlain:ResultInPlain? {
        if let resultInMarkdown = topicResultInMarkdown {
            return convertToPlainResult(from: resultInMarkdown)
        } else {
            return nil
        }
    }
    public var atUserResultsInPlain:[ResultInPlain] {
        return atUserResultsInMarkdown.map { result in
            return convertToPlainResult(from: result)
        }
    }
    public var allResultsInPlain:[ResultInPlain] {
        return allResultsInMarkdown.map { result in
            return convertToPlainResult(from: result)
        }
    }
    
    public var plainText:String {
        return refreshPlainText()
    }
    public func changePlainText(in range: NSRange, replacementText text: String) {
        let markdownRange = convertToMarkdownRange(from: range)
        if let rangeN = Range(markdownRange, in: markdownText) {
            markdownText.replaceSubrange(rangeN, with: text)
            checkRegularExpression()
        } else if markdownRange.location == markdownText.utf16.count {
            markdownText.append(text)
            checkRegularExpression()
        }
    }
    
    public func changeSelectionToAvailabelRange(from originPlainRange:NSRange) -> NSRange {
        let markdownRange = convertToMarkdownRange(from: originPlainRange)
        return convertToPlainRange(from: markdownRange)
    }
    ///返回值为，被删除的 #topic 在 plainText 中的 Range（删除前的 Range）
    @discardableResult
    public func deleteTopic() -> NSRange? {
        if let topicResult = topicResultInMarkdown, let topicRange = Range(topicResult.range, in: markdownText) {
            let plainRange = convertToPlainRange(from: topicResult.range)
            markdownText.replaceSubrange(topicRange, with: "")
            checkRegularExpression()
            return plainRange
        }
        return nil
    }
    public func insertTopic(_ name:String, id: Int, at range:NSRange) {
        let topicString = "#[" + name + "](tid=" + String(id) + ")#"
        let markdownRange = convertToMarkdownRange(from: range)
        if let rangeN = Range(markdownRange, in: markdownText) {
            markdownText.replaceSubrange(rangeN, with: topicString)
            checkRegularExpression()
        }
    }
    ///返回值为，被删除的 @user 在 plainText 中的 Range（删除前的 Range）
    @discardableResult
    public func deleteAtUser(id:String) -> NSRange? {
        for result in atUserResultsInMarkdown {
            if let range = Range(result.range, in: markdownText), let idRange = Range(result.range(at: 2), in: markdownText), id == markdownText[idRange] {
                let plainRange = convertToPlainRange(from: result.range)
                markdownText.replaceSubrange(range, with: "")
                checkRegularExpression()
                return plainRange
            }
        }
        return nil
    }
    public func insertAtUser(name:String, id:String, at range:NSRange) {
        let atUserString = "@[" + name + "](uid=" + id + ")@"
        let markdownRange = convertToMarkdownRange(from: range)
        if let rangeN = Range(markdownRange, in: markdownText) {
            markdownText.replaceSubrange(rangeN, with: atUserString)
            checkRegularExpression()
        }
    }
    
    private func convertToMarkdownRange(from plainRange: NSRange) -> NSRange {
        let startLocation = convertToMarkdownLocation(from: plainRange.location)
        if plainRange.length == 0 {
            return NSRange(location: startLocation, length: 0)
        }
        let endLocation = convertToMarkdownLocation(from: plainRange.location + plainRange.length)
        return NSRange(location: startLocation, length: endLocation - startLocation)
    }
    private func convertToPlainResult(from markdownResult:NSTextCheckingResult) -> ResultInPlain {
        let plainRange = convertToPlainRange(from: markdownResult.range)
        var range = NSRange(location: plainRange.location, length: plainRange.length)
        let result = NSTextCheckingResult.regularExpressionCheckingResult(ranges: &range, count: 1, regularExpression: markdownResult.regularExpression!)

        if markdownResult.numberOfRanges == 3, let nameRange = Range(markdownResult.range(at: 1), in: markdownText), let idRange = Range(markdownResult.range(at: 2), in: markdownText) {
            let name = markdownText[nameRange]
            let id = markdownText[idRange]
            return ResultInPlain(result: result, name: String(name), id: String(id))
        }
        return ResultInPlain(result: result, name: "", id: "")
    }
    private func convertToMarkdownLocation(from plainLocation: Int) -> Int {
        var offset = 0
        let allResultsInPlain = allResultsInMarkdown.map { result in
            return convertToPlainResult(from: result)
        }
        
        for i in 0..<allResultsInPlain.count {
            let plainResult = allResultsInPlain[i]
            let markdownResult = allResultsInMarkdown[i]
            let plainNameLocation = plainResult.result.range.location
            let plainNameLength = plainResult.result.range.length
            
            if plainNameLocation + plainNameLength <= plainLocation {//result 完全在前面
                offset += markdownResult.range.length - plainNameLength
            } else if plainLocation <= plainNameLocation {//result 完全在后面
                break
            } else {//重叠
                if plainLocation - plainNameLocation <= plainNameLocation + plainNameLength - plainLocation {//离前面近
                    //先对齐到前面
                    offset -= plainLocation - plainNameLocation
                } else {//离后面近
                    //先对齐到前面
                    offset -= plainLocation - plainNameLocation
                    //再对齐到后面
                    offset += markdownResult.range.length
                }
            }
        }
        return plainLocation + offset
    }
    
    private func convertToPlainRange(from markdownRange: NSRange) -> NSRange {
        let startLocation = convertToPlainLcation(from: markdownRange.location)
        if markdownRange.length == 0 {
            return NSRange(location: startLocation, length: 0)
        }
        let endLocation = convertToPlainLcation(from: markdownRange.location + markdownRange.length)
        return NSRange(location: startLocation, length: endLocation - startLocation)
    }
    private func convertToPlainLcation(from markdownLocation: Int) -> Int {
        var offset = 0
        for result in allResultsInMarkdown {
            let nameRange = result.range(at: 1)
            let fullNameLength = nameRange.length + 2
            if result.range.location + result.range.length <= markdownLocation {//result 完全在前面
                offset -= result.range.length - fullNameLength
            } else if markdownLocation <= result.range.location {//result 完全在后面
                break
            } else {//重叠
                if markdownLocation - result.range.location <= result.range.location + result.range.length - markdownLocation {//离前面近
                    //对齐到前面
                    offset -= markdownLocation - result.range.location
                } else {//离后面近
                    //先对齐到前面
                    offset -= markdownLocation - result.range.location
                    //再向后移动真实名称长度
                    offset += fullNameLength
                }
            }
        }
        return markdownLocation + offset
    }
    
    
    private func refreshPlainText() -> String {
        var text = markdownText
        for result in allResultsInMarkdown.reversed() {
            if let nameRange = Range(result.range(at: 1), in: text), let fullRange = Range(result.range, in: text) {
                var fullText = text[nameRange]
                if result == topicResultInMarkdown {
                    fullText = "#" + fullText + " "
                } else {
                    fullText = "@" + fullText + " "
                }
                text.replaceSubrange(fullRange, with: fullText)
            }
        }
        return text
    }
    func checkRegularExpression() {
        if let detectorT = try? NSRegularExpression(pattern: topicPattern, options:[]) {
            topicResultInMarkdown = detectorT.matches(in: markdownText, options: .withTransparentBounds, range: NSRange(location: 0, length: markdownText.utf16.count)).first
        }
        if let detectorA = try? NSRegularExpression(pattern: atUsersPattern, options:[]) {
            atUserResultsInMarkdown = detectorA.matches(in: markdownText, options: .withTransparentBounds, range: NSRange(location: 0, length: markdownText.utf16.count))
        }
    }
}
